

(() => {
    const $cache = (() => {
        const data = {};
        return {
            get(key, fn) {
                if (!key) {
                    return fn && fn(key);
                }
                if (key in data) {
                    return data[key];
                }
                if (!fn) {
                    return null;
                }
                return data[key] = fn(key);
            }
        };
    })();



    const jsonLoader = (() => {
        const cache = {}; // URL -> Promise<JSON>
        const PROCESSED = Symbol('processed'); // 自定义符号标记

        async function getJson(url) {
            if (!cache[url]) {
                cache[url] = (async () => {
                    const res = await fetch(url);
                    if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.statusText}`);
                    return res.json();
                })();
            }
            return cache[url];
        }

        function resolvePointer(obj, pointer, refUrl) {
            refUrl += pointer;
            if (cache[refUrl]) {
                return cache[refUrl];
            }
            // pointer 形如 "#/components/schemas/validation-error"
            const parts = pointer.replace(/^#\//, '').split('/');
            let current = obj;
            for (const part of parts) {
                if (current && part in current) {
                    current = current[part];
                } else {
                    console.warn(`Pointer ${pointer} not found`);
                    return cache[refUrl] = { "$ref(404)": pointer };
                }
            }
            current["_ref"] = pointer;
            return cache[refUrl] = current;
        }

        async function resolveRefs(obj, baseUrl, rootJson) {
            // 如果已经处理过，直接返回
            if (obj == null || (typeof obj === 'object' && obj[PROCESSED])) {
                return obj;
            }
            obj[PROCESSED] = true;
            if (Array.isArray(obj)) {
                return Promise.all(obj.map(item => resolveRefs(item, baseUrl, rootJson)));
            } else if (obj && typeof obj === 'object') {
                if (obj.$ref && typeof obj.$ref === 'string') {
                    let refUrl, pointer;
                    if (obj.$ref.startsWith('#')) {
                        // 本地引用
                        refUrl = baseUrl;
                        pointer = obj.$ref;
                    } else {
                        const [urlPart, pointerPart] = obj.$ref.split('#');
                        refUrl = new URL(urlPart, baseUrl).href;
                        pointer = pointerPart ? `#${pointerPart}` : '';
                    }
                    const targetJson = refUrl === baseUrl ? rootJson : await getJson(refUrl);
                    const targetObj = pointer ? resolvePointer(targetJson, pointer, refUrl) : targetJson;
                    return resolveRefs(targetObj, refUrl, targetJson);
                } else {
                    for (const [k, v] of Object.entries(obj)) {
                        obj[k] = await resolveRefs(v, baseUrl, rootJson);
                    }
                }
            }
            return obj;
        }

        async function get(url) {
            const absUrl = new URL(url, location.href).href;
            const json = await getJson(absUrl);
            return resolveRefs(json, absUrl, json);
        }

        return { get };
    })();




    // swagger 提供程序
    const swagger_providers = [
        {
            // 从全局变量 swagger_json 中获取 swagger.json 的内容
            canHandle: () => typeof swagger_json !== 'undefined' && swagger_json,
            handle: function (app) {
                const swagger = swagger_json;
                swagger.info = {
                    version: "1.0.0",
                    title: "",
                    server: "",
                    ...swagger.info
                };
                if (!swagger.servers || !swagger.servers.length) {
                    swagger.servers = [{ url: "" }];
                }
                swagger.success = true;
                app.swaggers = [Object.freeze(swagger)];
                app.swagger = app.swaggers[0];
            }
        },
        {
            // 如果参数中存在 site 参数, 则加载参数中的 {swaggers}.json 文件
            canHandle: () => location.search.indexOf("site=") >= 0,
            handle: function (app) {
                var list = location.search.split("site=")[1].split("&")[0] + ".txt";
                fetch(list + "?" + Date.now())
                    .then(response => {
                        if (!response.ok) {
                            alert("加载失败: " + response.statusText);
                            throw new Error(response.statusText);
                        }
                        return response.text();
                    })
                    .then(lines => swagger_providers.load(lines.split("\n").filter(x => x)));
            }
        },
        {
            // 从当前页面的 url 中获取 swagger.json 的地址
            canHandle: () => true,
            handle: function (app) {
                const urls = location.search.length > 1 ? location.search.split(/[?&]/).filter(x => x) : ["swagger.json"];
                swagger_providers.load(urls);
            }
        },
    ];

    swagger_providers.load = function (urls) {
        app.swaggers = urls.map(x => ({ url: x, info: { title: "加载中" }, loading: true }));

        const args = location.hash.slice(1).split("|");
        const swaggerId = decodeURIComponent(args[0] || '');
        const apiMethod = decodeURIComponent(args[1] || '');
        const apiPath = decodeURIComponent(args[2] || '');

        // 判断如果当前页面是https, 但 urls 中存在 http 的资源, 则 将当前网站 重定向到 http 的站点
        // if (location.protocol === "https:" && urls.some(x => x.startsWith("http:"))) {
        //     location.href = location.href.replace("https://", "http://");
        //     return;
        // }

        for (let i in urls) {
            const url = urls[i];
            jsonLoader.get(url)
                .then(swagger => {
                    console.log(url, " ---> ", swagger);

                    if (!swagger.servers || !swagger.servers.length) {
                        if (url.startsWith('//')) {
                            swagger.servers = [{ url: new URL(location.protocol + url).origin }];
                        } else if (url.startsWith('/')) {
                            swagger.servers = [{ url: location.protocol + "//" + location.host + url }];
                        } else if (url.startsWith('http://') || url.startsWith('https://')) {
                            swagger.servers = [{ url: new URL(url).origin }];
                        } else {
                            swagger.servers = [{ url: location.protocol + "//" + location.host + "/" + url }];
                        }
                    }

                    for (const server of swagger.servers) {
                        if (server.url.endsWith("/")) {
                            server.url = server.url.slice(0, -1);
                        }
                    }
                    for (const path in swagger.paths) {
                        if (!path.startsWith("/")) {
                            swagger.paths["/" + path] = swagger.paths[path];
                            delete swagger.paths[path];
                        }
                    }

                    swagger.info = {
                        title: url.split('/').pop(),
                        version: "1.0.0",
                        server: swagger.servers[0].url,
                        ...swagger.info
                    };
                    swagger.url = url;
                    swagger.success = true;
                    swagger.components || (swagger.components = {});
                    swagger.components.schemas || (swagger.components.schemas = {});
                    swagger.components.responses || (swagger.components.responses = {});
                    swagger.components.parameters || (swagger.components.parameters = {});
                    swagger.components.securitySchemes || (swagger.components.securitySchemes = {});

                    app.swaggers[i] = Object.freeze(swagger);
                    if (swaggerId === swagger.info.title + '-' + swagger.info.version) {
                        app.swagger = app.swaggers[i];
                        if (apiMethod && apiPath) {
                            app.active_group = app.api_groups.find(x => x.apis.some(x => x.method === apiMethod && x.path === apiPath));
                            app.active_api = app.active_group.apis.find(x => x.method === apiMethod && x.path === apiPath);
                        }
                    } else if (!swaggerId && !app.swagger) {
                        app.swagger = app.swaggers[i];
                    } else if (!app.swagger && app.swaggers.every(x => !x.loading)) {
                        app.swagger = app.swaggers[0];
                    }
                })
                .catch(error => {
                    console.error(url, " ---> ", error);
                    const name = url.split('/').pop();
                    app.swaggers[i] = { url: url, info: { title: name + " 加载失败" }, error: error };
                });

        }
    };




    const app = new Vue({
        el: "#app",
        data: {
            swagger: null,
            active_group: null,
            active_api: null,
            search: "",
            swaggers: [],
            show_swagger_list: false,
            active_request_body: null,
            active_response: null,
            active_response_body: null,
            show_api_description: false,
        },
        watch: {
            active_api(value) {
                this.show_api_description = false;
                this.active_request_body = value && value.requestBody && Object.values(value.requestBody.content)[0];
                this.active_response = value && Object.values(value.responses)[0];
                if (value) {
                    location.hash = '#' + this.swagger.info.title + '-' + this.swagger.info.version + '|' + value.method + '|' + value.path;
                }
            },
            active_response(value) {
                this.active_response_body = value && value.content && Object.values(value.content)[0];
            },
            swagger(value, oldValue) {
                if (!value || !value.success) {
                    this.openDialog("text", value && value.error);
                    if (oldValue && oldValue.success) {
                        this.swagger = oldValue;
                    }
                    return;
                }
                if (!this.active_group && this.api_groups.length === 1) {
                    this.active_group = this.api_groups[0];
                }
                document.getElementById("urlBase").href = value.info.server;
                if (!this.active_api) {
                    location.hash = '#' + value.info.title + '-' + value.info.version;
                }
            }
        },
        methods: {
            switchSwagger(sw) {
                this.clean();
                this.swagger = sw;
            },
            clean() {
                this.search = "";
                this.active_group = null;
                this.active_api = null;
                this.active_request_body = null;
                this.active_response = null;
                this.active_response_body = null;
                this.show_swagger_list = false;
                this.show_api_description = false;
            },
            get(props) {
                try {
                    var fn = new Function("a", "return a." + props);
                    return fn(this.swagger);
                } catch (error) {
                    return null;
                }
            },
            highlight(text) {
                if (!this.search || !text) {
                    return text.text || text;
                }
                if (text.apis && text.apis.some(x => !(x.path.toLowerCase().indexOf(this.search.toLowerCase()) === -1))) {
                    return text.text || text;
                }
                text = text.text || text;
                if (text.toLowerCase().indexOf(this.search.toLowerCase()) === -1) {
                    return null;
                }
                const regEx = new RegExp(this.search, 'gi');
                return text.replace(regEx, `<b class="highlight text">$&</b>`);
            },
            parseSchema(schema) {
                const schemas = [];
                for (const s of ([].concat(schema)).map(x => this.getSchema(x))) {
                    if (!schemas.find(x => x.key === s.key)) {
                        schemas.push(s);
                    }
                }
                for (let i = 0; i < schemas.length; i++) {
                    const schema = schemas[i];
                    for (const property of Object.values(schema.properties || {})) {
                        if (property._ref && !schemas.find(x => x.key === property._ref)) {
                            schemas.push(this.getSchema(property));
                        }
                        if (property.items && property.items._ref && !schemas.find(x => x.key === property.items._ref)) {
                            schemas.push(this.getSchema(property.items));
                        }
                    }
                    if (schema.items && schema.items._ref && !schemas.find(x => x.key === schema.items._ref)) {
                        schemas.push(this.getSchema(schema.items));
                    }
                }
                schemas.sort((a, b) => {
                    if (a.enum && !b.enum) {
                        return 1;
                    }
                    if (!a.enum && b.enum) {
                        return -1;
                    }
                    return 0;
                });
                return schemas;
            },
            getSchema(schema) {
                if (!schema) return null;
                if (schema.key) {
                    return schema;
                }
                if (schema.schema) {
                    schema = schema.schema;
                }

                const key = schema._ref && `${this.swagger.url}.getSchema(${schema._ref})`;
                return $cache.get(key) || $cache.get(key, key => {
                    let result = null;
                    if (schema._ref) {
                        const ref = schema._ref;
                        result = { ...schema, key: ref, name: this.getTypeName(ref) };
                    } else {
                        result = { ...schema, key: schema.type, name: schema.type };
                    }
                    if (this.isUploadFile(result)) {
                        result = { ...result, properties: { "-": { description: "文件上传", type: "file" } }, key: "file", name: "file", type: "file" };
                    }
                    if (result.enum) {
                        result.properties = {};
                        // ProductCategory_CategoryAttribute(商品品类_分类属性)=2
                        const regex = /(.+?)\((.*?)\)=([0-9]+)/;
                        for (const value of result.enum) {
                            const match = (value + "").match(regex);
                            if (match) {
                                result.properties[match[1]] = { description: match[2], type: result.format, default: match[3] };
                            } else {
                                result.properties[value] = { description: value, type: result.format || result.type };
                            }
                        }
                    }
                    return result;
                });
            },
            getTypeName(_ref) {
                if (!_ref) return "";
                // #/components/schemas/UFX.Cloud.Core.MyJson`1[[UFX.SCM.Cloud.InfrastructureCenter.Domain.Shared.Dtos.Response.Common.GetBusinessRelatedPersonResDto, UFX.SCM.Cloud.InfrastructureCenter.Domain.Shared, Version=3.0.64.0, Culture=neutral, PublicKeyToken=null]]"
                // 'MyJson<GetBusinessRelatedPersonResDto>'

                // #/components/schemas/UFX.Cloud.Core.MyJson`1[[System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
                // 'MyJson<Dictionary<String, String>>'

                const regex = /(`\d+)?\[([^\[\]]+)\]/;
                while (true) {
                    const match = _ref.match(regex);
                    if (!match) break;
                    if (match[1]) {
                        _ref = _ref.replace(match[0], `<${match[2]}>`);
                    } else {
                        const type = this.getTypeName(match[2]);
                        _ref = _ref.replaceAll ? _ref.replaceAll(match[0], type) : _ref.replace(match[0], type);
                    }
                }
                if (_ref.startsWith("#/components/schemas/")) {
                    _ref = _ref.split('/').pop();
                }
                // System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
                // <类型完整名称>, <程序集名称>, Version=<版本号>, Culture=<区域性>, PublicKeyToken=<公共密钥令牌>
                // ^((?:[^,<]+|<[^>]+>)+)
                return /^((?:[^,<]+|<[^>]+>)+)/.exec(_ref)[1].split('.').pop();
            },
            getTypeText(schema) {
                if (!schema) return "null";
                if (schema.type === "array") {
                    return this.getTypeText(schema.items) + "[]";
                }
                schema = this.getSchema(schema);
                const name = this.getTypeName(schema._ref) || schema.name || schema.type;

                if (!schema.enum && schema.format) {
                    return `${name}(${schema.format})`;
                }

                return name;
            },
            getVaildText(schema, name) {
                if (!schema) return "";
                schema = this.getSchema(schema);
                const root = name ? schema : {};
                schema = name ? schema.properties[name] : schema;
                schema = this.getSchema(schema);

                const text = [];

                // required, nullable, uniqueItems
                if (root["x-required-any"]) {
                    const required = root["x-required-any"].map(x => x.map(y => y.toLowerCase())).find(x => x.includes(name.toLowerCase()));
                    if (required) {
                        const names = Object.keys(root.properties).filter(x => required.some(y => y.toLowerCase() == x.toLowerCase()));
                        text.push(`必须[${names}]之一`);
                    } else if (schema.required) {
                        text.push("必须");
                    }
                } else if (schema.required) {
                    text.push("必须");
                }
                if (schema.nullable) {
                    text.push("可空");
                }
                if (schema.uniqueItems) {
                    text.push("唯一");
                }

                // minLength, maxLength, minItems, maxItems
                if (schema.minLength != null && schema.maxLength != null) {
                    text.push(`长度：${schema.minLength} ~ ${schema.maxLength}`);
                } else if (schema.minLength != null) {
                    text.push(`长度：> ${schema.minLength}`);
                } else if (schema.maxLength != null) {
                    text.push(`长度：< ${schema.maxLength}`);
                } else if (schema.minItems != null && schema.maxItems != null) {
                    text.push(`长度：${schema.minItems} ~ ${schema.maxItems}`);
                } else if (schema.minItems != null) {
                    text.push(`长度：> ${schema.minItems}`);
                } else if (schema.maxItems != null) {
                    text.push(`长度：< ${schema.maxItems}`);
                }

                // minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, enum
                if (schema.minimum != null || schema.maximum != null) {
                    let range = "";
                    range += exclusiveMinimum ? "[" : "(";
                    range += minimum != null ? minimum : "-∞";
                    range += ", ";
                    range += maximum != null ? maximum : "+∞";
                    range += exclusiveMaximum ? "]" : ")";
                    if (range === "(-∞, +∞)") {
                        range = "任意值";
                    }
                    text.push(`取值：${range}; ${(multipleOf && `${multipleOf}的倍数`)}`);
                } else if (schema.multipleOf) {
                    text.push(`取值：${schema.multipleOf}的倍数`);
                }

                if (schema.enum) {
                    if (schema.key && schema.key.charAt(0) === "#") {
                        text.push("枚举：跳转到定义处查看");
                    } else {
                        text.push(`枚举：${schema.enum.filter(x => x).join("/")}`);
                    }
                }

                if (schema.pattern) {
                    text.push(`正则：${schema.pattern}`);
                }

                return text.join("<br/>");
            },
            toJsonObject(schema) {
                if (!schema) return null;
                if (schema._ref) {
                    schema = this.getSchema(schema);
                }
                if (schema.type === "array") {
                    return [this.toJsonObject(schema.items)];
                }
                if (schema.type === "object") {
                    const obj = {};
                    for (const key in schema.properties) {
                        obj[key] = this.toJsonObject(schema.properties[key]);
                    }
                    return obj;
                }
            },
            toJsonString(schema) {
                return JSON.stringify(this.getExample(schema, new Set()), null, 2);
            },
            getExample(schema, context) {
                if (!schema) return null;
                if (schema.schema) {
                    schema = schema.schema;
                }
                if (schema._ref) {
                    schema = this.getSchema(schema);
                }
                if (schema.example) {
                    return schema.example;
                }
                if (schema.examples) {
                    for (const example of Object.values(schema.examples)) {
                        return example.value;
                    }
                }

                if (schema.default) {
                    return schema.default;
                }

                if (schema.type === "integer") {
                    return 0;
                }
                if (schema.type === "number") {
                    return 0.0;
                }
                if (schema.type === "boolean") {
                    return false;
                }
                if (schema.type === "string") {
                    switch (schema.format) {
                        case "date-time":
                            // yyyy-MM-dd HH:mm:ss
                            return new Date().toISOString().replace("T", " ").split(".")[0];
                        case "date":
                            // yyyy-MM-dd
                            return new Date().toISOString().split("T")[0];
                        case "uuid":
                            return "00000000-0000-0000-0000-000000000000";
                        case "byte":  // base64 encoded characters
                            return "base64"
                        case "binary": // any sequence of octets
                            return "octets"
                        case "password": // a hint to UIs to obscure input
                            return "***password***"
                        case "email":
                            return "mail@email.com";
                        default:
                            return schema.enum ? schema.enum[0] : "";
                    }
                }

                if (context && context.has(schema.key)) {
                    return schema.type === "array" ? [] : schema.properties && schema.properties.length ? {} : null;
                }

                if (schema.type === "array") {
                    if (!context) {
                        context = new Set();
                    }
                    context.add(schema.key);
                    var res = [this.getExample(schema.items, context)];
                    context.delete(schema.key);
                    return res;
                }
                if (schema.type === "object") {
                    if (!context) {
                        context = new Set();
                    }
                    context.add(schema.key);
                    const obj = {};
                    for (const key in schema.properties) {
                        obj[key] = this.getExample(schema.properties[key], context);
                    }
                    context.delete(schema.key);
                    return obj;
                }
                return null;
            },
            isUploadFile(schema) {
                if (!schema || schema._ref) return false;
                if (schema.type === "array") {
                    return this.isUploadFile(schema.items);
                }
                const fileSchema = {
                    "type": "object",
                    "name": "object",
                    "key": "object",
                    "properties": {
                        "ContentType": {
                            "type": "string"
                        },
                        "ContentDisposition": {
                            "type": "string"
                        },
                        "Headers": {
                            "type": "object",
                            "additionalProperties": {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            }
                        },
                        "Length": {
                            "type": "integer",
                            "format": "int64"
                        },
                        "Name": {
                            "type": "string"
                        },
                        "FileName": {
                            "type": "string"
                        }
                    }
                };
                return this.equals(schema, fileSchema);
            },
            equals(a, b) {

                if (a === b) return true;
                if (!a || !b) return false;

                if (typeof a === "object" && typeof b === "object") {
                    for (const key in a) {
                        if (!this.equals(a[key], b[key])) {
                            return false;
                        }
                    }
                    return true;
                }
                return false;
            },
            openDialog(contentType, content) {
                let text = "";
                if (contentType) {
                    if (contentType.includes("json")) {
                        text = this.toJsonString(content);
                    } else if (contentType.includes("form")) {
                        text = Object.entries(content).map(x => `${x[0]}=${encodeURIComponent(x[1])}`).join("&");
                    } else if (contentType.includes("text")) {
                        text = String(content);
                    }
                }
                if (!text) {
                    return;
                }
                const dialog = document.createElement("dialog");
                dialog.innerHTML = `<pre style="text-align: left;font-size:1.2em;">${text}</pre><button>Close</button>`;
                dialog.style.cssText = "text-align: center;";
                // 双击pre时, 全选pre中的文件
                dialog.querySelector("pre").ondblclick = () => {
                    const range = document.createRange();
                    range.selectNode(dialog.querySelector("pre"));
                    window.getSelection().removeAllRanges();
                    window.getSelection().addRange(range);
                };
                dialog.querySelector("button").onclick = () => dialog.remove();
                document.body.appendChild(dialog);
                dialog.showModal();
            },
            getHttpRequestMessage() {
                if (!this.active_api) return null;

                const query = Object.values(this.active_api.parameters.query).map(x => `${x.name}=${encodeURIComponent(this.getExample(x))}`).join("&");
                const protocol = this.swagger.info.server.split("://")[0] || "";
                const host = this.swagger.info.server.split("://")[1] || "";
                const text = [
                    `${this.active_api.method.toLocaleUpperCase()} ${this.active_api.path}${query ? ("?" + query) : ""}`,
                    `Host: ${host}${!host.includes(":") && protocol === "https" ? ":443" : ""}`,
                ];


                for (const parameter of Object.values(this.active_api.parameters.header)) {
                    text.push(`${parameter.name}: ${parameter.default || (parameter.required ? "" : "(可空)")}`);
                }

                if (this.active_request_body) {
                    const contentType = Object.entries(this.active_api.requestBody.content).find(x => x[1] === this.active_request_body)[0];
                    text.push(`Content-Type: ${contentType}`);
                    if (this.active_request_body.schema) {
                        text.push("");
                        if (contentType.includes("json")) {
                            text.push(this.toJsonString(this.active_request_body.schema));
                        } else if (contentType.includes("form")) {
                            text.push(Object.entries(this.active_request_body.schema.properties).map(x => `${x[0]}=${encodeURIComponent(this.getExample(x[1]))}`).join("&"));
                        }
                    }
                }

                return text.join("\n");
            },
            toSchemaView(schema) {
                schema = this.getSchema(schema);
                document.getElementById(schema.name).scrollIntoView({ behavior: "smooth" });
            }
        },
        computed: {
            title() {
                return this.get("info.title") || "加载中...";
            },
            version() {
                return this.get("info.version");
            },
            api_groups() {
                const swagger = this.swagger;
                if (!swagger) return null;

                const key = `${swagger.url}.api_groups`;
                return $cache.get(key) || $cache.get(key, () => {

                    const groups = (swagger.tags || []).map(x => ({ ...x }));

                    for (const path in swagger.paths) {
                        const methods = swagger.paths[path];
                        for (const method in methods) {
                            const api = { ...methods[method], path, method };
                            api.text = api.summary || api.path;
                            for (const tag of api.tags || ["No Group"]) {
                                let group = groups.find(x => x.name === tag);
                                if (!group) {
                                    groups.push(group = { name: tag });
                                }
                                if (!group.apis) {
                                    group.apis = [];
                                    group.text = group.description || group.name
                                }
                                if (!group.description) {
                                    group.description = group.name;
                                }
                                group.apis.push(api);
                            }

                            const parameters = { "path": [], "query": [], "header": [], "cookie": [], "formData": [] };

                            if (swagger.components.securitySchemes) {
                                for (const parameter of Object.values(swagger.components.securitySchemes)) {
                                    if (parameter.in) {
                                        parameters[parameter.in].push(parameter);
                                    }
                                }
                            }

                            for (const parameter of api.parameters || []) {
                                if (!parameters[parameter.in]) {
                                    parameters[parameter.in] = [];
                                }
                                parameters[parameter.in].push(parameter);
                            }

                            api.parameters = parameters;
                        }
                    }
                    return groups.filter(x => x.apis && x.apis.length);

                });
            }
        },
    });
    document.body.classList.remove("loading");
    document.getElementById("app").style.display = "";

    async function LoadSwagger() {
        for (const provider of swagger_providers) {
            var canHandle = provider.canHandle();
            if (canHandle instanceof Promise) {
                canHandle = await canHandle;
            }

            if (canHandle) {
                provider.handle(app);
                break;
            }
        }
    }

    LoadSwagger();

    if (location.hostname === "localhost" || location.protocol === "file:") {
        window.app = app;
    }

    window.addEventListener("click", e => {
        const target = e.target;
        if (target.tagName === "A") {
            const href = target.getAttribute("href") || '#';
            if (href === '#') {
                e.preventDefault();
            }
        }
    }, true);

})();